﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NeuralNetworkPro.Activation_Functions;

namespace NeuralNetworkPro
{
    /// <summary>
    /// Base neural layer class
    /// </summary>
    /// 
    /// <remarks>This is a base neural layer class, which represents
    /// collection of neurons.</remarks>
    /// 
    [Serializable]
    public class Layer
    {
        private int index;

        public int Index
        {
            get { return index; }
            private set { index = value; }
        }
        

        /// <summary>
        /// Layer's inputs count
        /// </summary>
        protected int inputsCount = 0;

        /// <summary>
        /// Layer's neurons count
        /// </summary>
        protected int neuronsCount = 0;

        /// <summary>
        /// Layer's neurons
        /// </summary>
        protected Neuron[] neurons;

        /// <summary>
        /// Layer's output vector
        /// </summary>
        protected double[] output;

        /// <summary>
        /// Layer's inputs count
        /// </summary>
        public int InputsCount
        {
            get { return inputsCount; }
        }

        /// <summary>
        /// Layer's neurons count
        /// </summary>
        public int NeuronsCount
        {
            get { return neuronsCount; }
        }

        /// <summary>
        /// Layer's output vector
        /// </summary>
        /// 
        /// <remarks>The calculation way of layer's output vector is determined by
        /// inherited class.</remarks>
        /// 
        public double[] Output
        {
            get { return output; }
        }

        /// <summary>
        /// Layer's neurons accessor
        /// </summary>
        /// 
        /// <param name="index">Neuron index</param>
        /// 
        /// <remarks>Allows to access layer's neurons.</remarks>
        /// 
        public Neuron this[int index]
        {
            get { return neurons[index]; }
            private set { neurons[index] = value; }
        }

        /// <summary>
        /// Compute output vector of the layer 
        /// </summary>
        /// 
        /// <param name="input">Input vector</param>
        /// 
        /// <returns>Returns layer's output vector</returns>
        /// 
        /// <remarks>The actual layer's output vector is determined by inherited class and it
        /// consists of output values of layer's neurons. The output vector is also stored in
        /// <see cref="Output"/> property.</remarks>
        /// 
        public double[] Compute(double[] input)
        {
            var new_input = input.Concat(new double[] { -1 }).ToArray();
            // compute each neuron
            for (int i = 0; i < neuronsCount; i++)
                output[i] = neurons[i].Compute(new_input);

            return output;
        }

        public static Layer InputLayer(int neuronsCount, int inputsCount, IActivationFunction function)
        {
            var res = new Layer(0, neuronsCount, inputsCount, function);
            for (int i = 0; i < neuronsCount; i++)
            {
                res[i] = new InputNeuron(i);
            }
            return res;
        }
		
		/// <summary>
		/// Initializes a new instance of the <see cref="ActivationLayer"/> class
		/// </summary>
		/// <param name="neuronsCount">Layer's neurons count</param>
		/// <param name="inputsCount">Layer's inputs count</param>
		/// <param name="function">Activation function of neurons of the layer</param>
		/// 
		/// <remarks>The new layet will be randomized (see <see cref="ActivationNeuron.Randomize"/>
		/// method) after it is created.</remarks>
		/// 
		public Layer( int index, int neuronsCount, int inputsCount, IActivationFunction function )
							
		{
            this.index = index;
            this.inputsCount = Math.Max(1, inputsCount);
            this.neuronsCount = Math.Max(1, neuronsCount);
            // create collection of neurons
            neurons = new Neuron[this.neuronsCount];
            // allocate output array
            output = new double[this.neuronsCount];
			// create each neuron
			for ( int i = 0; i < neuronsCount; i++ )
				neurons[i] = new Neuron( inputsCount + 1, function );
		}

        internal void HasRemovedNeuron(int idx)
        {
            this.inputsCount--;
            for (int i = 0; i < this.NeuronsCount; i++)
            {
                this[i].RemoveImput(idx);
            }
        }

        internal void RemoveNeuron(int idx)
        {
            if (this.neuronsCount <= idx)
            {
                throw new Exception("Error in dimension");
            }
            this.neuronsCount--;
            this.neurons = this.neurons.RemoveIdx(idx).ToArray();
            output = new double[neuronsCount];
        }
    }
}